package com.zhongge.customarraylist;

import java.util.Random;

/**
 * @ClassName MyArrayList
 * @Description TODO
 * @Author zhongge
 * @Version 1.0
 */
@SuppressWarnings("all")
public class MyArrayList {
    private static final int DEFAULT_CAPACITY = 10;//无参构造的默认数组容量,默认容量为10
    private int capacity;//自定义容量，也是用于到时候判断:容量*(3/4)是否需要扩容
    private int usedSize;//存放有效元素的个数，可作为下标始终指向最后一个有效元素的下一个
    String[] array; //数组
    public MyArrayList() {
        this.capacity = DEFAULT_CAPACITY;//表示当前容量
        array = new String[DEFAULT_CAPACITY];
    }

    public MyArrayList(int capacity) {
        this.capacity = capacity;//记录当前的容量
        array = new String[this.capacity];
    }

    /**
     * 1.增
     * @param data
     * @return
     */
    public boolean add(String data) {
        //判断容量以及扩容
        //注意：为什么使用usedSize + 1?
        //原因就是说我提前预判存入下一个元素之后是否需要扩容，应为一旦执行了add都会将该元素存进去的
        ensureCapacity(usedSize + 1);//ensure:翻译为确保
        //将数据存放在集合中
        array[usedSize++] = data;
        return true;//存放成功
    }

    /**
     * 增的一个辅助函数：判断容量
     * @param index
     */
    private void ensureCapacity(int index) {
        if (index >= capacity * (3.0/4)) {
            grow();
            System.out.println("数组长度=" + capacity);
        }
    }

    /**
     * 增的辅助函数:扩容
     */
    private void grow() {
        //1.将容量进行扩容1.5倍
        capacity = capacity + (capacity>>1);
        //2.创建新的数组
        String[] newArray = new String[capacity];
        //3.将旧数组中的值拷到新的数组中
        for (int i = 0;  i < usedSize; i++) {//由于usedSize始终指向最后一个元素的下一个，所以考到有效数组就是<=usedSize-1或者就是<usedSize
            newArray[i] = array[i];
        }
        //4.array存放新数组的地址
        array = newArray;
    }

    /**
     * 2.将内容输出
     * @return
     */
    @Override
    public String toString() {
        //这部分知识使用到了字符串String中的知识，如果忘记了就要回去看
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        //判断进行输出的逻辑
        /**
         * 为什么是i<usedSize?
         * 1.使用usedSize是因为我们只需要打印有效数据
         * 2.使用<是因为有效数据只是到达usedSize-1
         */
        for (int i = 0; i < usedSize; i++) {
            stringBuilder.append(array[i]);
            if (i != usedSize - 1) {
                stringBuilder.append(",");
            }
        }
        stringBuilder.append("]");
        return stringBuilder.toString();//将stringBuilder变为String
    }

    /**
     * 3.根据索引index进行数据的查询
     * 有索引==》判断合法性
     * @param index
     * @return
     */
    public String get(int index) {
        //首先判断下标是否合法
        isLawful(index);
        //返回查找的数据
        return array[index];
    }

    /**
     * 专门用于判断下标是否合法(由于只供给本类使用所以才使用private)
     * @param index
     * @return
     */
    private void isLawful(int index) {
        //这里使用了异常相关知识
        /**
         * 为什么使用 index >= array.length？而不用index > array.length？
         *  原因：数组的下标只是到达array.length - 1
         *  一旦到达array.length了，那么说明就已经越界了，所以才取=号
         */
        if (index < 0 || index >= array.length) {
            //抛出数组下标越界异常
            throw new ArrayIndexOutOfBoundsException();
        }
        /*
        补充异常的相关知识：
            异常：
                定义一个异常类
                发生异常的类
            在本例中：异常类使用的是jdk自带的ArrayIndexOutOfBoundsException
                    发生异常的类是：本类MyArrayList或者是本类MyArrayList中的ifLawful方法
         */
    }

    /**
     *4. 根据指定的索引，修改指定索引处的值
     * @param index 索引
     * @param value 新的值
     */
    public void updata(int index, String value) {
        //1.有索引那么毫无疑问必须先要判断合法性
        isLawful(index);

        //程序走到这里说明索引合法
        array[index] = value;
    }

    /**
     * 5.根据下标删除指定出的元素
     * @param index 下标
     * @return 返回已经删除后的值
     */
    public String remove(int index) {
        //1.有索引 毫无疑问需要判断合法性
        isLawful(index);
        //如果程序走到这里说明合法
        String tmp = array[index];//将旧值先存起来，到时候直接返回
        for (int i = index; i < usedSize; i++) {
            array[i] = array[i + 1];
        }
        //如果我的下标是在usedSize之内的话 我就直接将有效元素usedSize--
        //否则我就直接将没有元素所存储的地方返回(底层是null)
        if (index < usedSize) {
            usedSize--;
        }
        return tmp;
    }

    /**
     * 6.返回集合整有效元素的个数
     * @return 有效元素的个数
     */
    public int size() {
        return usedSize;
    }

    /**
     * 7.根据传入的对象返回该对象所出现的第一次的下标
     * @param str 对象
     * @return 对象所在位置的下标，如果不存在，则返回-1
     */
    public int indexOf(String str) {
        //判断str是否为空
        if (str == null) {
            return -1;
        }
        //遍历查找
        for (int i = 0; i < usedSize; i++) {
            if (str.equals(array[i])) {
                return i;
            }
        }
        //程序如果走到这里，那么说明找不到
        return -1;
    }

    /**
     * 8.判断集合中是否有元素
     * @return 为空返回true 不为空返回false
     */
    public boolean isEmpty() {
        return usedSize == 0;
    }

    /**
     * 9.判断元素是否包含在集合中
     * @param value
     * @return 在返回true 不在返回false
     */
    public boolean contains(String value) {
        return indexOf(value) != -1;//如果不等于-1说明在集合中返回true
    }

    /**
     * 10.统计此元素在集合中出现了多少次
     * @param value
     * @return 次数
     */
    public int count(String value) {
        if (value == null) {
            return 0;
        }
        int tmp = 0;
        for (int i = 0; i < usedSize; i++) {
            if (value.equals(array[i])){
                tmp++;
            }
        }
        return tmp;
    }

    /**
     * 11.倒转数组元素
     */
    public void reverse() {
        //使用双指针算法

        for (int left = 0, right = usedSize - 1; left < right; left++, right--) {
            String tmp = array[left];
            array[left] = array[right];
            array[right] = tmp;
        }
    }

    /***************补充洗牌中的方法****************/
    /**
     * 随机打乱数组中的元素
     */
    public void shuffle() {
        //产生一个随机数
        Random random = new Random();
        int randoeIndex = random.nextInt(usedSize);
        //进行随机交换
        swap(randoeIndex);
    }

    private void swap(int index) {
        for (int i = 0; i < usedSize; i++) {
            String tmp = array[i];
            array[i] = array[index];
            array[index] = tmp;
        }
    }
    /*********************************************/
}

